/*
 * Students:
 * Massimiliano Rango - 610826
 * Martina Benedetto - 610962
 * 
 * A.A 2011/2012
 * 
 */ 

import java.util.Vector;

/**
 *
 * This class is a thread which represents every Node. It is an abstract class 
 * because we can't create Node with type neither RED nor LSM. In other words, 
 * in this way we can't create an instance of class Node, the Node must be of 
 * type RED or LSM. So, the methods implemented by this abstract class are the 
 * ones in common between the two child classes.
 * 
 * @version 1.0 27 Dec 2011
 * @author Martina Benedetto 
 *
 */

public abstract class Node extends Thread {
	
	public Object lockIncoming;
	public Object lockKilled;
	
	protected Configuration m_conf;
	
	private int m_ID;
	private Position m_pos;
	private int m_remainingEnergy;
	private Vector<Node> m_neighbours;
	private Vector<Message> m_incoming;
	private Vector<ControlMessage> m_archive;
	private boolean m_killed;
	private int m_sentmessages;
	private int m_receivedmessages;
	private int m_signatures;
	
	public Node(int ID, Position pos, Configuration conf) {
		m_ID = ID;
		m_pos = pos;
		m_conf = conf;
		m_remainingEnergy = m_conf.getTotEnergy();
		m_neighbours  = new Vector<Node>();
		m_incoming = new Vector<Message>();
		m_archive = new Vector<ControlMessage>();
		m_killed = false;
		lockIncoming = new Object();
		lockKilled = new Object();
		m_sentmessages = 0;
		m_receivedmessages = 0;
		m_signatures = 0;
	}

	/**
	 * Removes from circulation as many messages as there are in the in the 
	 * incoming vector and sets the killed flag of this node to true.
	 */
	protected void die() {
		synchronized(lockKilled) {
			m_conf.removeCirculatingMess(m_incoming.size());
			m_incoming.clear();
			m_killed = true;
		}
	}
	
	/**
	 * @param mess Message to send
	 * @param to Node receiver
	 * @param newCirculatingMessage Indication if mess is a new message or 
	 * it has been forwarded
	 * @return null if to has already died, otherwise returns the indication
	 *  if this node has sent the message 
	 */
	protected Boolean sendMessage(Message mess, Node to, boolean newCirculatingMessage) {
		synchronized(to.lockKilled) {
			if(to.isKilled()) {
				removeNeighbour(to);
				return null;
			}
			m_remainingEnergy -= m_conf.getSendEnergy();
			if(m_remainingEnergy >= 0) {
				to.addIncoming(mess, newCirculatingMessage);
				addSentMessage();
			}
		}
		if(m_remainingEnergy <= 0) {	/*the Node is dead*/
			die();
		}
		return m_remainingEnergy >= 0;
	}
	
	/**
	 * @param cm Control Message to send to nearest
	 * @param nearest Node receiver
	 * @param newCirculatingMessage Indication if cm is a new message or it has been forwarded
	 * @return Indication if the message has been sent (the receiver was not dead) 
	 */
	protected boolean sendToNearest(ControlMessage cm, Node nearest, 
			boolean newCirculatingMessage) {
		if(nearest == this) {	
			if(!newCirculatingMessage) {
				m_conf.removeCirculatingMess(1);
			}
			return true;
		} 
		Boolean sent = sendMessage(cm, nearest, newCirculatingMessage);
		if(!newCirculatingMessage && sent != null && !sent) {
			m_conf.removeCirculatingMess(1);
		}
		return sent != null;
	}
	
	/**
	 * Removes the first message from the incoming vector 
	 * @return The first message from the incoming vector if this node was 
	 * able to receive it, otherwise null
	 */
	protected Message receiveMessage() {
		Message m = null;
		synchronized(lockIncoming) {
			m_remainingEnergy -= m_conf.getRecEnergy();
			if(m_remainingEnergy >= 0) {
				m = m_incoming.remove(0);
				addReceivedMessage();
				if(m_remainingEnergy == 0) {
					m_conf.removeCirculatingMess(1);
				}
			}
		}
		if(m_remainingEnergy <= 0) {
			die();
		}
		return m;
	}
	
	/**
	 * @param pos Destination position
	 * @return The nearest node to pos in neighbours vector 
	 */
	protected Node nearest(Position pos) {
		Node nearest = this;
		for(int i = 0; i < m_neighbours.size();) {
			synchronized(m_neighbours.get(i).lockKilled) {
				if(m_neighbours.get(i).m_killed) {
					m_neighbours.remove(i);
				}
				else {
					if(m_neighbours.get(i).getPosition().distanceFrom(pos) 
							< nearest.getPosition().distanceFrom(pos)) {
						nearest = m_neighbours.get(i);
					}
					i++;
				}
			}
		}
		return nearest;
	}
	
	/**
	 * Checks if there is a clone in the network, considering the last 
	 * control message added to the archive vector 
	 * @param newCirculatingMessage Indication if the last message added to
	 * the archive vector is a new message or it has been forwarded
	 */
	protected void cloneDetection(boolean newCirculatingMessage) {
		boolean cloneFound = false;
		if(m_archive.size() > 1) {
			ControlMessage m = m_archive.lastElement();
			m_remainingEnergy -= m_conf.getSigEnergy(); /* signature of m */
			addSignature();
			for(int i = 0 ; m_remainingEnergy > 0 && !cloneFound && i < m_archive.size(); i++) {
				if(i != m_archive.size() - 1) {
					m_remainingEnergy -= m_conf.getSigEnergy(); /*signature of m_archive.get(i)*/
					addSignature();
					if(m_remainingEnergy >= 0) {
						if(m_archive.get(i).getID() == m.getID() && !m_archive.get(i).getPosition().equals(m.getPosition())){
							cloneFound = true;
						}
					}
				}
			}
			if(m_remainingEnergy <= 0) {
				die();
				if(!newCirculatingMessage) {
					m_conf.removeCirculatingMess(1);
				}
			}
			if(cloneFound)
				m_conf.setCloneFound();
		}
	}
	
	public boolean isKilled() {
		synchronized(lockKilled) {
			return m_killed;
		}
	}
	
	public void setKilled() {
		synchronized(lockKilled) {
			m_killed = true;
		}
	}
	
	public String toString() {
		return m_ID + " " + m_pos;
	}
	
/* SIZE METHODS */
	
	/**
	 * 
	 * @return the size of the vector m_neighbours. In other words, how many
	 * neighbours the current Node has
	 */
	public int sizeNeighbours() {
		return m_neighbours.size();
	}
	
	/**
	 * 
	 * @return the size of the vector m_archive. In other words, how many 
	 * messages are stored in the archive
	 */
	public int sizeArchive() {
		return m_archive.size();
	}
	
	/**
	 * 
	 * @return the size of the vector m_incoming. In other words, how many 
	 * messages are incoming-messages
	 */
	public int sizeIncoming() {
		synchronized(lockIncoming) {
			return m_incoming.size();
		}
	}
	
/* END SIZE METHODS */
	
/* ADD METHODS */
	
	/**	
	 * @param m Message to add to the incoming vector
	 * @param newCirculatingMessage Indication if m is a new message or it 
	 * has been forwarded 
	 */
	public void addIncoming(Message m, boolean newCirculatingMessage) {
		synchronized(lockIncoming) {
			if(!isKilled()) {
				m_incoming.add(m);
				if(newCirculatingMessage)
					m_conf.addCirculatingMess();
				lockIncoming.notifyAll();
			}
		}
	}
	
	/**
	 * @param neighbour Node to add at the neighbours vector
	 * @return Indication if the node has been added
	 */
	public boolean addNeighbour(Node neighbour) {
		if(m_neighbours.contains(neighbour)) {
			return false;
		}
		m_neighbours.add(neighbour);
		return true;
	}
	
	public void addArchive(ControlMessage m) {
		m_archive.add(m);
	}
	
	public void addSentMessage() {
		m_sentmessages++;
	}
	
	public void addReceivedMessage() {
		m_receivedmessages++;
	}
	
	public void addSignature() {
		m_signatures++;
	}
	
/* END ADD METHODS */
	
/* REMOVE METHODS */
	
	/**
	 * @param neighbour Node to remove from the neighbours vector
	 * @return indication if the node has been removed
	 */
	public boolean removeNeighbour(Node neighbour) {
		return m_neighbours.remove(neighbour);
	}
	
	/**
	 * 
	 * @param i the index of the vector in which the Node to remove from 
	 * neighbours is located
	 * @return indication if the node has been removed
	 */
	public Node removeNeighbour(int i) {
		return m_neighbours.remove(i);
	}
	
/* END REMOVE METHODS */
	
/* GET METHODS */
	
	public Message getIncoming(int i) {
		synchronized(lockIncoming) {
			return m_incoming.get(i);
		}
	}
	
	public int getSentMessages(){
		return m_sentmessages;
	}
	
	public int getReceivedMessages(){
		return m_receivedmessages;
	}
    
	public int getSignatures(){
		return m_signatures;
	}
	
	public int getID() {
		return m_ID;
	}
	
	public Position getPosition() {
		return m_pos;
	}
	
	public Configuration getConfiguration() {
		return m_conf;
	}
	
	public int getRemainingEnergy(){
		return m_remainingEnergy;
	}
	
	public Node getNeighbour(int i) {
		return m_neighbours.get(i);
	}
	
/* END GET METHODS */

}
